package com.dove.authority.service.impl;

import cn.hutool.core.util.StrUtil;
import com.dove.authority.Async.AuthorityAsync;
import com.dove.authority.entity.User;
import com.dove.authority.entity.dto.UserLoginPasswordDto;
import com.dove.authority.entity.dto.UserLoginPhoneDto;
import com.dove.authority.entity.dto.UserRegisterDto;
import com.dove.authority.entity.vo.UserVo;
import com.dove.authority.mapper.RoleMapper;
import com.dove.authority.mapper.UserMapper;
import com.dove.authority.service.CaptchaService;
import com.dove.authority.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dove.authority.utils.IpAddressUtil;
import com.dove.authority.utils.RedisUtil;
import com.dove.authority.utils.TokenManage;
import com.dove.entity.GlobalException;
import com.dove.entity.MySimpleGrantedAuthority;
import com.dove.entity.Result;
import com.dove.entity.UserDetailsImpl;
import com.dove.util.SecurityContextUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author run
 * @since 2021-03-18
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private CaptchaService captchaService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private TokenManage tokenManage;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private AuthorityAsync authorityAsync;

    @Autowired
    private RoleMapper roleMapper;

    @Value("${spring.redis.expire-time.phone}")
    private static String EXPIRE_TIME;

    @Value("${jwt.expire-time}")
    private static String JWT_EXPIRE_TIME;

    @Value("${spring.redis.content-key.user-details}")
    private static String USER_DETAIL_KEY;

    @Value("${spring.redis.content-key.error.phone}")
    private static String ERROR_CODE_COUNT_KEY;

    @Value("${spring.redis.content-key.error.password}")
    private static String ERROR_PASSWORD_COUNT_KEY;

    @Value("${spring.redis.max-count.error.password}")
    private static String MAX_ERROR_PASSWORD_COUNT;

    @Value("${spring.redis.max-count.error.phone}")
    private static String MAX_ERROR_CODE_COUNT;

    private final static String DEFAULT_AVATAR = "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png";

    private InnerUtil util = new InnerUtil();

    @Override
    public boolean register(UserRegisterDto userDto, HttpServletRequest request, HttpServletResponse response) {
        String ip = IpAddressUtil.getIpAddress(request);
        if (StrUtil.isEmpty(ip)){
            throw new GlobalException(Result.error("非法ip访问"));
        }

        int userCount = userMapper.findUserCountOfPhone(userDto.getPhone());

        if (userCount > 0){
            throw new GlobalException(Result.error("该手机号已被使用"));
        }

        //TODO 获取redis中的短信验证码
        String originCode = "";

        if (!userDto.getCode().equals(originCode)){
            throw new GlobalException(Result.error("短信验证码输入错误"));
        }


        User user = new User(userDto.getPhone()
                                , encoder.encode(userDto.getPassword())
                                , 1
                                , DEFAULT_AVATAR);

        int result = userMapper.insert(user);

        if (result < 1){
            throw new GlobalException(Result.error("注册失败，请重试"));
        }

        //生成JWT
        String token = tokenManage.createToken(user.getId(), util.createClaims(user));

        //将JWT放入response
        util.setTokenToResponse(token, response);

        //将userDetails放入redis
        util.setUserDetails(user);


        return true;
    }

    @Override
    public boolean login(UserLoginPhoneDto userDto, HttpServletRequest request, HttpServletResponse response) {

        String ip = IpAddressUtil.getIpAddress(request);

        if (redisUtil.getCountOfKey(ERROR_CODE_COUNT_KEY + ip) >= Integer.valueOf(MAX_ERROR_CODE_COUNT)){
            throw new GlobalException(Result.error("验证码错误次数已达上限，请稍后再试"));
        }

        //TODO 获取短信验证码
        String originCode = "";

        if (!userDto.getCode().equals(originCode)){
            //验证码错误，更新redis里的记录次数
            redisUtil.redisIncrement(ERROR_CODE_COUNT_KEY + ip, EXPIRE_TIME);
            throw new GlobalException(Result.error("手机验证码输入错误"));
        }

        User user = userMapper.findUserByPhone(userDto.getPhone());

        if (user == null){
            throw new GlobalException(Result.error("该用户不存在"));
        }

        String token = tokenManage.createToken(user.getId(), util.createClaims(user));
        util.setTokenToResponse(token, response);

        //将userDetails放入redis
        util.setUserDetails(user);

        return true;
    }

    @Override
    public boolean login(UserLoginPasswordDto userDto, HttpServletRequest request, HttpServletResponse response) {

        String ip = IpAddressUtil.getIpAddress(request);

        //先对图形验证码进行验证
        captchaService.verifyCode(ip, userDto.getcaptcha());

        if (redisUtil.getCountOfKey(ERROR_PASSWORD_COUNT_KEY + ip) >= Integer.valueOf(MAX_ERROR_PASSWORD_COUNT)){
            throw new GlobalException(Result.error("密码错误次数已达上限，请稍后再试"));
        }

        User user = userMapper.findUserByPhone(userDto.getPhone());

        if (user == null){
            throw new GlobalException(Result.error("该用户不存在"));
        }

        if (!user.getPassword().equals(encoder.encode(userDto.getPassword()))){
            //密码输入错误，在redis中更新错误次数
            redisUtil.redisIncrement(ERROR_PASSWORD_COUNT_KEY + ip, EXPIRE_TIME);
            throw new GlobalException(Result.error("验证码输入错误"));
        }

        String token = tokenManage.createToken(user.getId(), util.createClaims(user));
        util.setTokenToResponse(token, response);

        //将userDetails放入redis
        util.setUserDetails(user);

        return true;
    }

    @Override
    public boolean updateUser(User user) {
        UserDetailsImpl userDetails = SecurityContextUtil.getUserDetails();
        user.setId(userDetails.getId());
        return userMapper.updateById(user) > 0;
    }

    @Override
    public UserVo getUserInfo() {
        UserDetailsImpl userDetails = SecurityContextUtil.getUserDetails();

        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(userDetails, userVo);

        return userVo;
    }

    private class InnerUtil{

        public void setTokenToResponse(String token, HttpServletResponse response){
            response.setHeader("token", token);
            response.setHeader("Access-Control-Expose-Headers", "token");
        }

        public Map<String, Object> createClaims(User user){
            Map<String, Object> claims = new HashMap<>(6, 1);
            claims.put("userId", user.getId());
            claims.put("userName", user.getUsername());
            claims.put("enterpriseId", user.getEnterpriseId());
            claims.put("userSex", user.getSex());
            claims.put("phone", user.getPhone());
            claims.put("profilePhoto",user.getProfilePhoto());
            return claims;
        }

        public void setUserDetails(User user){
            UserDetailsImpl userDetails = new UserDetailsImpl();
            BeanUtils.copyProperties(user, userDetails);

            //获取用户的角色id列表
            List<Long> roleIds = roleMapper.findRoleIdsOfUser(userDetails.getId());
            Set<MySimpleGrantedAuthority> authorities = new HashSet<>();
            Lock lock = new ReentrantLock();
            CountDownLatch count = new CountDownLatch(roleIds.size());

            roleIds.forEach(roleId -> authorityAsync.getAuthorityOfRole(roleId, count, lock, authorities));

            try {
                count.await(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                throw new GlobalException(Result.error("权限信息填充失败"));
            }

            //设置权限信息
            userDetails.setAuthorities(new ArrayList<>(authorities));

            redisUtil.add(USER_DETAIL_KEY + user.getId(), userDetails, JWT_EXPIRE_TIME);
        }

    }
}

